﻿#region Header

// // <copyright file="UnifyComponents.cs">
// // Copyright (c) 2013, Nicolas Anderlini
// // All rights reserved.
// // 
// // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// // 
// //     - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
// //     - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
// //     - Neither the name of Nicolas Anderlini nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
// // 
// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// // </copyright>
// // <author>Nicolas Anderlini</author>
// // <email>nicolas.anderlini@live.fr</email>
// // <date>07-30-2013</date>
// // <summary></summary>

#endregion

#region

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

#endregion

namespace Unify
{
    /// <summary>
    /// </summary>
    public class UnifyComponents : IUnifyComponents
    {
        /// <summary>
        ///     Assemblies where we are going to operate
        /// </summary>
        private readonly List<Assembly> _assemblies;

        /// <summary>
        ///     Conf
        /// </summary>
        private readonly IUnifyConfiguration _unifyConfiguration;


        /// <summary>
        ///     Configure Unify with default configuration section name
        /// </summary>
        public UnifyComponents(IUnifyConfiguration unifyConfiguration)
        {
            if (null == unifyConfiguration)
                throw new ConfigurationMissingException("Unify Configuration instance is missing");
            //set config
            _unifyConfiguration = unifyConfiguration;
            //load reals assemblies
            _assemblies = new List<Assembly>();
            foreach (string assemblyName in _unifyConfiguration.GetAssembliesName())
            {
                Assembly loadedAssembly = Assembly.Load(assemblyName);
                _assemblies.Add(loadedAssembly);
            }
        }


        /// <summary>
        ///     Get a type present in the loaded assemblies
        /// </summary>
        /// <param name="typeName">name of type</param>
        /// <returns></returns>
        public Type GetUnifyType(string typeName)
        {
            //load reals assembly types
            foreach (Assembly a in _assemblies)
            {
                Type customLifetimeManagerType = a.GetType(typeName);
                if (null != customLifetimeManagerType)
                    return customLifetimeManagerType;
            }
            return null;
        }

        /// <summary>
        ///     Return all types which must be declared in the container
        /// </summary>
        /// <returns></returns>
        public IDictionary<Type, UnifyContract> GetUnifyComponents()
        {
            //search annoted types
            IEnumerable<Type> types = GetAnnotedTypes();

            //possible interfaces by implementation
            var interfaces = new Dictionary<Type, IEnumerable<Type>>();
            //name for each implementation
            var names = new Dictionary<Type, string>();
            foreach (var type in types)
            {
                if (!type.IsAbstract)
                {
                    //calculate possible interfaces by implementation
                    var typeContract = new List<Type>();
                    typeContract.AddRange(type.GetInterfaces().Where(this.VerifyTypePresentInAssemblies));
                    //interfaces
                    typeContract.AddRange(types.Where(at => type.IsSubclassOf(at))); //abstract types

                    var typeAbs = type.BaseType;
                    if (null != typeAbs)
                    {
                        typeContract.Add(typeAbs);
                    }
                    typeContract.Add(type);

                    interfaces.Add(type, typeContract);

                    //process names for each implementations
                    switch (this.GetNamingFromType(type))
                    {
                        case(TypeNaming.None):
                            names.Add(type, null);
                            break;
                        case(TypeNaming.Classname):
                            names.Add(type, type.Name);
                            break;
                    }
                }
            }

            //calcultate dependancies injection in each implementation
            var dependencies = new Dictionary<Type, IEnumerable<string>>();
            foreach (var contract in interfaces)
            {
                var injectionMember = new List<string>();
                var props = //get properties of current class and parent, which is public and settable
                    contract.Key.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty)
                            .Where(
                                prop =>
                                interfaces.Any(
                                    c =>
                                    c.Value.Contains(prop.PropertyType) && !(prop.PropertyType.Equals(typeof (object))) &&
                                    VerifyConstraints(contract.Key, c.Key, prop.Name)));
                injectionMember.AddRange(props.Select(prop => prop.Name));
                //injectionMember.ForEach(a => Console.WriteLine("Injection property: {0}", a.ToString()));
                dependencies.Add(contract.Key, injectionMember);
            }


            return interfaces.ToDictionary(i => i.Key,
                                           i =>
                                           new UnifyContract(interfaces[i.Key], dependencies[i.Key],
                                                             GetLifetimeNameFromType(i.Key), names[i.Key]));
        }

        /// <summary>
        ///     Verify that a type is contained in unify loaded assemblies
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private bool VerifyTypePresentInAssemblies(Type type)
        {
            return _assemblies.Contains(type.Assembly);
        }

        /// <summary>
        ///     Get unify annoted types from loaded assemblies
        /// </summary>
        /// <returns></returns>
        private IEnumerable<Type> GetAnnotedTypes()
        {
            var types = new List<Type>();
            foreach (var assembly in _assemblies)
            {
                types.AddRange(assembly.GetTypes().Where(type => Attribute.IsDefined(type, typeof (UnifyComponent))));
            }
            return types;
        }

        /// <summary>
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private string GetLifetimeNameFromType(Type type)
        {
            var unifyComponent = (UnifyComponent) Attribute.GetCustomAttribute(type, typeof (UnifyComponent));
            return _unifyConfiguration.GetLifetimeNameFromStrategyName(unifyComponent.Strategy);
        }


        /// <summary>
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private TypeNaming GetNamingFromType(Type type)
        {
            var unifyComponent = (UnifyComponent)Attribute.GetCustomAttribute(type, typeof(UnifyComponent));
            return unifyComponent.Naming;
        }

        /// <summary>
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private string GetLayerNameFromType(Type type)
        {
            var unifyComponent = (UnifyComponent) Attribute.GetCustomAttribute(type, typeof (UnifyComponent));
            return _unifyConfiguration.GetLayerNameFromStrategyName(unifyComponent.Strategy);
        }

        /// <summary>
        /// </summary>
        /// <param name="typeTarget"></param>
        /// <param name="typeDependency"></param>
        /// <returns></returns>
        private bool VerifyConstraints(Type typeTarget, Type typeDependency, string propertyProblem)
        {
            var verifiyTargetLayerName = GetLayerNameFromType(typeTarget);
            var verifiyDependencyLayerName = GetLayerNameFromType(typeDependency);
            bool result = _unifyConfiguration.VerifyConstraints(verifiyTargetLayerName, verifiyDependencyLayerName);
            if (!result)
                throw new LayerConstraintNotRespectedException(
                    String.Format(
                        "Layers Constraints are not respected between {0} and {1} on property {2} from layers {3} and {4} (Types: {5} and {6})",
                        typeTarget.Name,
                        typeDependency.Name, propertyProblem, verifiyTargetLayerName, verifiyDependencyLayerName,
                        typeTarget, typeDependency));
            return true;
        }
    }
}